Document on_block() validate-then-mutate structure and resolve stale TODOs#154
Document on_block() validate-then-mutate structure and resolve stale TODOs#154pablodeymo wants to merge 2 commits intomainfrom
Conversation
🤖 Kimi Code ReviewReview SummaryThe PR improves documentation and clarifies the two-phase validation approach in Critical Issues
Security Concerns
Code Quality
Suggested Fixes// Move state mutation after all validation
let state_root = block.state_root;
// Don't modify post_state here - do it after validation
// After all validation passes:
post_state.latest_block_header.state_root = state_root;
// Then proceed with mutationsConsider wrapping the entire mutation phase in a transaction or implementing rollback mechanisms for critical failures. Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code ReviewNo functional changes detected — this diff only updates comments in Notes:
Given the above, I have no requested changes. The PR looks good from a code‑review perspective. Automated review by OpenAI Codex · custom prompt |
Greptile SummaryThis PR makes documentation-only improvements to
All changes are comments-only with zero executable code modifications. The explanations are technically accurate and improve code maintainability. Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/store.rs | Documentation-only changes that clarify the validate-then-mutate structure and remove three incorrect TODOs, replacing them with accurate explanations of protocol behavior |
Last reviewed commit: 4122c73
🤖 Claude Code ReviewI have everything I need. Here is the review: PR 154 Review: Document
|
|
Superseded by #157 |
…fied variants (#157) ## Motivation PR #113 proposed extracting signature verification out of `on_block()` into a separate `precheck_block_signatures()` function called by the network layer before processing. Four automated reviewers flagged a critical security flaw: removing `verify_signatures()` from `on_block()` means any caller that forgets to call `precheck_block_signatures()` first can import blocks without cryptographic validation. There are 3 call sites for `on_block()` (production `process_block()`, and 2 test files), and only 1 would get the precheck. PR #154 responded by taking the conservative approach: keep `on_block()` unchanged and document its validate-then-mutate structure. It argued against extraction. This PR is the compromise: instead of removing verification from `on_block()`, we **keep `on_block()` as the safe default** (always verifies) and add `on_block_without_verification()` as an explicit opt-out. This follows Zeam's architecture where `ForkChoice.onBlock()` is signature-agnostic and verification happens at the chain layer, but inverted — verification is the default and opting out requires calling a function whose name makes the risk obvious. This eliminates the `skip-signature-verification` compile-time feature flag entirely. ## Description ### `store.rs` — Split `on_block()` Three functions replace the old `on_block()`: | Function | Visibility | Verifies? | Stores sigs? | Used by | |----------|-----------|-----------|-------------|---------| | `on_block()` | public | Yes | Yes | Production (`lib.rs`), `signature_spectests` | | `on_block_without_verification()` | public | No | No | `forkchoice_spectests` | | `on_block_core()` | private | — | — | Shared logic for both | `on_block_core()` takes `parent_state` by value (avoids the previous `.clone()` — minor perf win). The 4-line dedup + parent-state preamble is duplicated in both public functions to avoid double deserialization (verification needs the state, core needs the state). ### `store.rs` — Split `on_gossip_attestation()` Same pattern: | Function | Visibility | Verifies? | Stores sigs? | |----------|-----------|-----------|-------------| | `on_gossip_attestation()` | public | Yes | Yes | | `on_gossip_attestation_without_verification()` | public | No | No | Both share the validation + `on_attestation()` + logging logic. Some duplication is acceptable to keep each function self-contained. ### `Cargo.toml` — Remove feature flag - Removed `[features]` section with `skip-signature-verification` - Removed `required-features = ["skip-signature-verification"]` from forkchoice test entry ### `forkchoice_spectests.rs` - Changed `store::on_block()` → `store::on_block_without_verification()` - Fixed 4 pre-existing `collapsible_if` clippy warnings (now surfaced because the test no longer requires a feature flag to compile, so `clippy --all-targets` sees it) ### `Makefile` - Removed `--features skip-signature-verification` from `test` target ### `CLAUDE.md` and `RELEASE.md` - Removed all `skip-signature-verification` references from test commands, gotchas, and Docker build examples ## Relationship to #113 and #78 This PR supersedes #113's approach. Rather than extracting verification to a separate precheck (which creates the "forgotten precheck" security risk flagged by reviewers), it splits the function into safe and unsafe variants. The safe variant (`on_block()`) always verifies — callers can't accidentally skip verification. The unsafe variant (`on_block_without_verification()`) makes the risk explicit in its name. Issue #78's concern about signature verification is fully addressed: production paths always verify, and the compile-time feature flag is eliminated. ## How to test ```bash make fmt make lint make test cargo test -p ethlambda-blockchain --test forkchoice_spectests cargo test -p ethlambda-blockchain --test signature_spectests --release ``` Confirm no feature flag remnants: ```bash grep -r "skip-signature-verification" crates/ ``` Note: `signature_spectests` have pre-existing failures (fixture deserialization issue with `ProposerSignature`). These failures are identical on `main` and are unrelated to this change. --------- Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com>
Summary
on_block()that was previously implicitContext
This is a follow-up to the investigation started in #113 by @MegaRedHand (which addresses #78). That PR proposed extracting signature verification from
on_block()into a separateprecheck_block_signatures()function called earlier in the pipeline. Four automated reviewers flagged a critical issue: removingverify_signatures()fromon_block()means any caller that forgets to callprecheck_block_signatures()first can import blocks without cryptographic validation. There are 3 call sites foron_block()(productionprocess_block(), and 2 test files), and only 1 would get the precheck.During this rework, we also attempted the two other TODOs in the function: rejecting blocks when block body or proposer attestations fail validation. Both turned out to be incorrect — forkchoice spec tests revealed that attestation validation failures are normal and expected:
source.slot > target.slotwhen the attesting validator's justified checkpoint (source) advanced past their chosen target. 3 spec tests (test_reorg_on_newly_justified_slot,test_all_validators_attest_in_single_aggregation,test_multiple_specs_same_target_merge_into_one) exercise this exact scenario.The current warn-and-skip behavior (
let _ = on_attestation(...)) is the correct design.Changes
crates/blockchain/src/store.rs(comments only, zero executable code changes):Updated
on_block()docstring — documents the two-phase structure:Removed TODO: "extract signature verification to a pre-checks function" (was at line 352) — signature verification stays inside
on_block(), which is the only safe design. Extracting it creates a "forgotten precheck" risk across all 3 call sites.Removed TODO: "fail the block if an attestation is invalid" (was at line 385) — replaced with a comment explaining that block body attestations fail validation for legitimate reasons (unseen forks, source exceeding target after justification advances).
Removed TODO: "validate attestations before processing" (was at line 434) — replaced with a comment explaining that proposer attestations can reference the block being processed or have stale checkpoint data.
Added phase boundary marker (
// === Store mutations ===) at the exact point where store mutations begin, making the validate-then-mutate structure grep-able.Relationship to #113 and #78
This PR supersedes #113's approach. Rather than extracting verification to a separate precheck (which creates safety risks), it documents that the current structure already achieves the goal: all validation happens before any store mutations, so
on_block()either rejects early (no side effects) or succeeds fully. Issue #78's concern about "accepting blocks with invalid attestations" is addressed by documenting that this is intentional protocol behavior, not a bug.Test plan
make fmtpassesmake lintpasseslet _ =(test_reorg_on_newly_justified_slot,test_all_validators_attest_in_single_aggregation,test_multiple_specs_same_target_merge_into_one)main(pre-existing fixture deserialization issue, unrelated)